Hu3sky's blog

[Untitled Post]

Word count: 10,555 / Reading time: 42 min
2018/04/25 Share

通用爬虫(Broad Crawls)

Scrapy默认对特定爬取进行优化。这些站点一般被一个单独的Scrapy spider进行处理, 不过这并不是必须或要求的(例如,也有通用的爬虫能处理任何给定的站点)。

除了这种爬取完某个站点或没有更多请求就停止的”专注的爬虫”,还有一种通用的爬取类型,其能爬取大量(甚至是无限)的网站, 仅仅受限于时间或其他的限制。 这种爬虫叫做”通用爬虫(broad crawls)”,一般用于搜索引擎。

通用爬虫一般有以下通用特性:

  • 其爬取大量(一般来说是无限)的网站而不是特定的一些网站。
  • 其不会将整个网站都爬取完毕,因为这十分不实际(或者说是不可能)完成的。相反,其会限制爬取的时间及数量。
  • 其在逻辑上十分简单(相较于具有很多提取规则的复杂的spider),数据会在另外的阶段进行后处理(post-processed)
  • 其并行爬取大量网站以避免被某个网站的限制所限制爬取的速度(为表示尊重,每个站点爬取速度很慢但同时爬取很多站点)。

正如上面所述,Scrapy默认设置是对特定爬虫做了优化,而不是通用爬虫。不过, 鉴于其使用了异步架构,Scrapy对通用爬虫也十分适用。 本篇文章总结了一些将Scrapy作为通用爬虫所需要的技巧, 以及相应针对通用爬虫的Scrapy设定的一些建议。

增加并发

并发是指同时处理的request的数量。其有全局限制和局部(每个网站)的限制。

Scrapy默认的全局并发限制对同时爬取大量网站的情况并不适用,因此您需要增加这个值。 增加多少取决于您的爬虫能占用多少CPU。 一般开始可以设置为100 。不过最好的方式是做一些测试,获得Scrapy进程占取CPU与并发数的关系。 为了优化性能,您应该选择一个能使CPU占用率在80%-90%的并发数。

增加全局并发数:

CONCURRENT_REQUESTS = 100

增加Twisted IO线程池的最大大小

目前,Scrapy以使用线程池的阻塞方式进行DNS解析。在并发级别较高的情况下,爬网可能会很慢,甚至会导致DNS解析器超时失败。增加处理DNS查询的线程数的可能解决方案。DNS队列的处理速度将更快,加快建立连接和整体爬行。

要增加最大线程池大小,请使用:

REACTOR_THREADPOOL_MAXSIZE = 20

设置你自己的DNS

如果您有多个爬网流程和单个中央DNS,它可能会像DNS服务器上的DoS攻击一样,从而导致整个网络变慢甚至阻塞您的计算机。为了避免这种情况,您可以使用本地缓存设置您自己的DNS服务器,并将其上传到OpenDNS或Verizon等大型DNS。

降低log级别

当进行通用爬取时,一般您所注意的仅仅是爬取的速率以及遇到的错误。 Scrapy使用INFOlog级别来报告这些信息。为了减少CPU使用率(及记录log存储的要求), 在生产环境中进行通用爬取时您不应该使用DEBUGlog级别。 不过在开发的时候使用DEBUG应该还能接受。

设置Log级别:

LOG_LEVEL = 'INFO'

禁止cookies

除非您 真的 需要,否则请禁止cookies。在进行通用爬取时cookies并不需要, (搜索引擎则忽略cookies)。禁止cookies能减少CPU使用率及Scrapy爬虫在内存中记录的踪迹,提高性能。

禁止cookies:

COOKIES_ENABLED = False

禁止重试

对失败的HTTP请求进行重试会减慢爬取的效率,尤其是当站点响应很慢(甚至失败)时, 访问这样的站点会造成超时并重试多次。这是不必要的,同时也占用了爬虫爬取其他站点的能力。

禁止重试:

RETRY_ENABLED = False

减小下载超时

如果您对一个非常慢的连接进行爬取(一般对通用爬虫来说并不重要), 减小下载超时能让卡住的连接能被快速的放弃并解放处理其他站点的能力。

减小下载超时:

DOWNLOAD_TIMEOUT = 15

禁止重定向

除非您对跟进重定向感兴趣,否则请考虑关闭重定向。 当进行通用爬取时,一般的做法是保存重定向的地址,并在之后的爬取进行解析。 这保证了每批爬取的request数目在一定的数量, 否则重定向循环可能会导致爬虫在某个站点耗费过多资源。

关闭重定向:

REDIRECT_ENABLED = False

启用 “Ajax Crawlable Pages” 爬取

有些站点(基于2013年的经验数据,之多有1%)声明其为 ajax crawlable 。 这意味着该网站提供了原本只有ajax获取到的数据的纯HTML版本。 网站通过两种方法声明:

在url中使用#! - 这是默认的方式;
使用特殊的meta标签 - 这在”main”, “index” 页面中使用。
Scrapy自动解决(1);解决(2)您需要启用AjaxCrawlMiddleware:

AJAXCRAWL_ENABLED = True
通用爬取经常抓取大量的 “index” 页面; AjaxCrawlMiddleware能帮助您正确地爬取。 由于有些性能问题,且对于特定爬虫没有什么意义,该中间默认关闭。

借助Firefox来爬取

这里介绍一些使用Firefox进行爬取的点子及建议,以及一些帮助爬取的Firefox实用插件。

在浏览器中检查DOM的注意事项

Firefox插件操作的是活动的浏览器DOM(live browser DOM),这意味着当您检查网页源码的时候, 其已经不是原始的HTML,而是经过浏览器清理并执行一些Javascript代码后的结果。 Firefox是个典型的例子,其会在table中添加 <tbody> 元素。 而Scrapy相反,其并不修改原始的HTML,因此如果在XPath表达式中使用 <tbody> ,您将获取不到任何数据。

所以,当XPath配合Firefox使用时您需要记住以下几点:

  • 当检查DOM来查找Scrapy使用的XPath时,禁用Firefox的Javascrpit。
  • 永远不要用完整的XPath路径。使用相对及基于属性(例如 idclasswidth 等)的路径 或者具有区别性的特性例如 contains(@href, 'image')
  • 永远不要在XPath表达式中加入 <tbody> 元素,除非您知道您在做什么

对爬取有帮助的实用Firefox插件

Firebug

Firebug 是一个在web开发者间很著名的工具,其对抓取也十分有用。 尤其是 检查元素(Inspect Element) 特性对构建抓取数据的XPath十分方便。 当移动鼠标在页面元素时,您能查看相应元素的HTML源码。

查看 使用Firebug进行爬取 ,了解如何配合Scrapy使用Firebug的详细教程。

XPather

XPather 能让你在页面上直接测试XPath表达式。

XPath Checker

XPath Checker 是另一个用于测试XPath表达式的Firefox插件。

Tamper Data

Tamper Data 是一个允许您查看及修改Firefox发送的header的插件。Firebug能查看HTTP header,但无法修改。

Firecookie

Firecookie 使得查看及管理cookie变得简单。您可以使用这个插件来创建新的cookie, 删除存在的cookie,查看当前站点的cookie,管理cookie的权限及其他功能。

使用Firebug进行爬取

介绍

本文档介绍了如何使用 Firebug (一个Firefox的插件)来使得爬取更为简单,有趣。 更多有意思的Firefox插件请参考 对爬取有帮助的实用Firefox插件 。 使用Firefox插件检查页面需要有些注意事项: 在浏览器中检查DOM的注意事项

在本样例中将展现如何使用 FirebugGoogle Directory 来爬取数据。Google Directory 包含了 入门教程 里所使用的 Open Directory Project 中一样的数据,不过有着不同的结构。

Firebug提供了非常实用的 检查元素 功能。该功能允许您将鼠标悬浮在不同的页面元素上, 显示相应元素的HTML代码。否则,您只能十分痛苦的在HTML的body中手动搜索标签。

在下列截图中,您将看到 检查元素 的执行效果。

firebug1

首先我们能看到目录根据种类进行分类的同时,还划分了子类。

不过,看起来子类还有更多的子类,而不仅仅是页面显示的这些,所以我们接着查找:

firebug2

正如路径的概念那样,子类包含了其他子类的链接,同时也链接到实际的网站中。

获取到跟进(follow)的链接

查看路径的URL,我们可以看到URL的通用模式(pattern):

http://directory.google.com/Category/Subcategory/Another_Subcategory

了解到这个消息,我们可以构建一个跟进的链接的正则表达式:

1
directory\.google\.com/[A-Z][a-zA-Z_/]+$

因此,根据这个表达式,我们创建第一个爬取规则:

1
2
3
4
Rule(LinkExtractor(allow='directory.google.com/[A-Z][a-zA-Z_/]+$', ),
'parse_category',
follow=True,
),

Rule 对象指导基于 CrawlSpider 的spider如何跟进目录链接。parse_category 是spider的方法,用于从页面中处理也提取数据。

spider的代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
from scrapy.contrib.linkextractors import LinkExtractor
from scrapy.contrib.spiders import CrawlSpider, Rule

class GoogleDirectorySpider(CrawlSpider):
name = 'directory.google.com'
allowed_domains = ['directory.google.com']
start_urls = ['http://directory.google.com/']

rules = (
Rule(LinkExtractor(allow='directory\.google\.com/[A-Z][a-zA-Z_/]+$'),
'parse_category', follow=True,
),
)

def parse_category(self, response):
# write the category page data extraction code here
pass

提取数据

现在我们来编写提取数据的代码。

在Firebug的帮助下,我们将查看一些包含网站链接的网页(以 http://directory.google.com/Top/Arts/Awards/ 为例), 找到使用 Selectors提取链接的方法。 我们也将使用 Scrapy shell 来测试得到的XPath表达式,确保表达式工作符合预期。

firebug3

正如您所看到的那样,页面的标记并不是十分明显: 元素并不包含 idclass 或任何可以区分的属性。所以我们将使用等级槽(rank bar)作为指示点来选择提取的数据,创建XPath。

使用Firebug,我们可以看到每个链接都在 td 标签中。该标签存在于同时(在另一个 td)包含链接的等级槽(ranking bar)的 tr 中。

所以我们选择等级槽(ranking bar),接着找到其父节点(tr),最后是(包含我们要爬取数据的)链接的 td

对应的XPath:

1
//td[descendant::a[contains(@href, "#pagerank")]]/following-sibling::td//a

使用 Scrapy终端 来测试这些复杂的XPath表达式,确保其工作符合预期。

简单来说,该表达式会查找等级槽的 td 元素,接着选择所有 td 元素,该元素拥有子孙 a 元素,且 a 元素的属性 href 包含字符串 #pagerank

当然,这不是唯一的XPath,也许也不是选择数据的最简单的那个。 其他的方法也可能是,例如,选择灰色的链接的 font 标签。

最终,我们编写 parse_category() 方法:

1
2
3
4
5
6
7
8
9
10
11
def parse_category(self, response):

# The path to website links in directory page
links = response.xpath('//td[descendant::a[contains(@href, "#pagerank")]]/following-sibling::td/font')

for link in links:
item = DirectoryItem()
item['name'] = link.xpath('a/text()').extract()
item['url'] = link.xpath('a/@href').extract()
item['description'] = link.xpath('font[2]/text()').extract()
yield item

注意,您可能会遇到有些在Firebug找到,但是在原始HTML中找不到的元素, 例如典型的 <tbody> 元素, 或者Firebug检查活动DOM(live DOM)所看到的元素,但元素由javascript动态生成,并不在HTML源码中。 (原文语句乱了,上面为意译- -: or tags which Therefer in page HTML sources may on Firebug inspects the live DOM )

调试内存溢出

在Scrapy中,类似Requests, Response及Items的对象具有有限的生命周期: 他们被创建,使用,最后被销毁。

这些对象中,Request的生命周期应该是最长的,其会在调度队列(Scheduler queue)中一直等待,直到被处理。 更多内容请参考 架构概览

由于这些Scrapy对象拥有很长的生命,因此将这些对象存储在内存而没有正确释放的危险总是存在。 而这导致了所谓的”内存泄露”。

为了帮助调试内存泄露,Scrapy提供了跟踪对象引用的机制,叫做 trackref, 或者您也可以使用第三方提供的更先进内存调试库 Guppy (更多内容请查看下面)。而这都必须在 Telnet终端 中使用。

内存泄露的常见原因

内存泄露经常是由于Scrapy开发者在Requests中(有意或无意)传递对象的引用(例如,使用 meta 属性或request回调函数),使得该对象的生命周期与 Request的生命周期所绑定。这是目前为止最常见的内存泄露的原因, 同时对新手来说也是一个比较难调试的问题。

在大项目中,spider是由不同的人所编写的。而这其中有的spider可能是有”泄露的”, 当所有的爬虫同时运行时,这些影响了其他(写好)的爬虫,最终,影响了整个爬取进程。

如果您没有正确释放(以前分配的)资源,泄漏也可能来自您编写的自定义中间件,管道或扩展。例如,如果您为每个进程运行多个蜘蛛,分配资源spider_opened 但不释放它们spider_closed可能会导致问题。

请求过多?

默认情况下,Scrapy将请求队列保存在内存中; 它包含 Request对象和Request属性中引用的所有对象(例如in meta)。虽然不一定是泄漏,但这可能需要很多内存。启用 持久作业队列可以帮助控制内存使用情况。

使用 trackref 调试内存泄露

trackref 是Scrapy提供用于调试大部分内存泄露情况的模块。 简单来说,其追踪了所有活动(live)的Request, Request, Item及Selector对象的引用。

您可以进入telnet终端并通过 prefs() 功能来检查多少(上面所提到的)活跃(alive)对象。 pref()print_live_refs() 功能的引用:

1
2
3
4
5
6
7
8
9
telnet localhost 6023

>>> prefs()
Live References

ExampleSpider 1 oldest: 15s ago
HtmlResponse 10 oldest: 1s ago
Selector 2 oldest: 0s ago
FormRequest 878 oldest: 7s ago

正如所见,报告也展现了每个类中最老的对象的时间(age)。

如果您有内存泄露,那您能找到哪个spider正在泄露的机会是查看最老的request或response。 您可以使用 get_oldest() 方法来获取每个类中最老的对象, 正如此所示(在终端中)(原文档没有样例)。

哪些对象被追踪了?

trackref 追踪的对象包括以下类(及其子类)的对象:

  • scrapy.http.Request
  • scrapy.http.Response
  • scrapy.item.Item
  • scrapy.selector.Selector
  • scrapy.spider.Spider

真实例子

让我们来看一个假设的具有内存泄露的准确例子。

假如我们有些spider的代码中有一行类似于这样的代码:

1
2
return Request("http://www.somenastyspider.com/product.php?pid=%d" % product_id,
callback=self.parse, meta={referer: response})

代码中在request中传递了一个response的引用,使得reponse的生命周期与request所绑定, 进而造成了内存泄露。

让我们来看看如何使用 trackref 工具来发现哪一个是有问题的spider(当然是在不知道任何的前提的情况下)。

当crawler运行了一小阵子后,我们发现内存占用增长了很多。 这时候我们进入telnet终端,查看活跃(live)的引用:

1
2
3
4
5
6
7
>>> prefs()
Live References

SomenastySpider 1 oldest: 15s ago
HtmlResponse 3890 oldest: 265s ago
Selector 2 oldest: 0s ago
Request 3878 oldest: 250s ago

事实上有很多现场responses(并且它们太旧了)肯定是可疑的,因为与responses相比,Requests应该具有相对较短的生命周期。responses的数量与Requests的数量相似,因此看起来它们以某种方式相关联。我们现在可以去检查蜘蛛的代码来发现产生泄漏的令人讨厌的线(在请求中传递响应引用)。

有时候关于活动对象的额外信息会有所帮助。让我们来检查最老的回应:

1
2
3
4
>>> from scrapy.utils.trackref import get_oldest
>>> r = get_oldest('HtmlResponse')
>>> r.url
'http://www.somenastyspider.com/product.php?pid=123'

如果你想遍历所有的对象,而不是获取最老的对象,你可以使用这个scrapy.utils.trackref.iter_all()函数:

1
2
3
4
5
>>> from scrapy.utils.trackref import iter_all
>>> [r.url for r in iter_all('HtmlResponse')]
['http://www.somenastyspider.com/product.php?pid=123',
'http://www.somenastyspider.com/product.php?pid=584',
...

很多spider?

如果您的项目有很多的spider,prefs() 的输出会变得很难阅读。针对于此, 该方法具有 ignore 参数,用于忽略特定的类(及其子类)。例如,这不会显示任何对蜘蛛的实时引用:

1
2
>>> from scrapy.spiders import Spider
>>> prefs(ignore=Spider)

scrapy.utils.trackref模块

以下是 trackref 模块中可用的方法。

  • classscrapy.utils.trackref.object_ref

    如果您想通过 trackref 模块追踪活跃的实例,继承该类(而不是对象)。

  • scrapy.utils.trackref.print_live_refs(class_name, ignore=NoneType)

    打印活跃引用的报告,以类名分类。参数:ignore (类或者类的元组) – 如果给定,所有指定类(或者类的元组)的对象将会被忽略。

  • scrapy.utils.trackref.get_oldest(class_name)

    返回给定类名的最老活跃(alive)对象,如果没有则返回 None 。首先使用print_live_refs() 来获取每个类所跟踪的所有活跃(live)对象的列表。

  • scrapy.utils.trackref.iter_all(class_name)

    返回一个能给定类名的所有活跃对象的迭代器,如果没有则返回 None。首先使用 print_live_refs() 来获取每个类所跟踪的所有活跃(live)对象的列表。

使用Guppy调试内存泄露

trackref 提供了追踪内存泄露非常方便的机制,其仅仅追踪了比较可能导致内存泄露的对象 (Requests, Response, Items及Selectors)。然而,内存泄露也有可能来自其他(更为隐蔽的)对象。 如果是因为这个原因,通过 trackref 则无法找到泄露点,您仍然有其他工具: Guppy library

如果使用 setuptools , 您可以通过下列命令安装Guppy:

1
easy_install guppy

telnet终端也提供了快捷方式(hpy)来访问Guppy堆对象(heap objects)。 下面给出了查看堆中所有可用的Python对象的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
>>> x = hpy.heap()
>>> x.bytype
Partition of a set of 297033 objects. Total size = 52587824 bytes.
Index Count % Size % Cumulative % Type
0 22307 8 16423880 31 16423880 31 dict
1 122285 41 12441544 24 28865424 55 str
2 68346 23 5966696 11 34832120 66 tuple
3 227 0 5836528 11 40668648 77 unicode
4 2461 1 2222272 4 42890920 82 type
5 16870 6 2024400 4 44915320 85 function
6 13949 5 1673880 3 46589200 89 types.CodeType
7 13422 5 1653104 3 48242304 92 list
8 3735 1 1173680 2 49415984 94 _sre.SRE_Pattern
9 1209 0 456936 1 49872920 95 scrapy.http.headers.Headers
<1676 more rows. Type e.g. '_.more' to view.>

您可以看到大部分的空间被字典所使用。接着,如果您想要查看哪些属性引用了这些字典, 您可以:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
>>> x.bytype[0].byvia
Partition of a set of 22307 objects. Total size = 16423880 bytes.
Index Count % Size % Cumulative % Referred Via:
0 10982 49 9416336 57 9416336 57 '.__dict__'
1 1820 8 2681504 16 12097840 74 '.__dict__', '.func_globals'
2 3097 14 1122904 7 13220744 80
3 990 4 277200 2 13497944 82 "['cookies']"
4 987 4 276360 2 13774304 84 "['cache']"
5 985 4 275800 2 14050104 86 "['meta']"
6 897 4 251160 2 14301264 87 '[2]'
7 1 0 196888 1 14498152 88 "['moduleDict']", "['modules']"
8 672 3 188160 1 14686312 89 "['cb_kwargs']"
9 27 0 155016 1 14841328 90 '[1]'
<333 more rows. Type e.g. '_.more' to view.>

如上所示,Guppy模块十分强大,不过也需要一些关于Python内部的知识。关于Guppy的更多内容请参考 Guppy documentation.

Leaks without leaks

有时候,您可能会注意到Scrapy进程的内存占用只在增长,从不下降。不幸的是, 有时候这并不是Scrapy或者您的项目在泄露内存。这是由于一个已知(但不有名)的Python问题。 Python在某些情况下可能不会返回已经释放的内存到操作系统。关于这个问题的更多内容请看:

改进方案由Evan Jones提出,在 这篇文章 中详细介绍,在Python 2.5中合并。 不过这仅仅减小了这个问题,并没有完全修复。引用这片文章:

不幸的是,这个patch仅仅会释放没有在其内部分配对象的区域(arena)。这意味着 碎片化是一个大问题。某个应用可以拥有很多空闲内存,分布在所有的区域(arena)中, 但是没法释放任何一个。这个问题存在于所有内存分配器中。解决这个问题的唯一办法是 转化到一个更为紧凑(compact)的垃圾回收器,其能在内存中移动对象。 这需要对Python解析器做一个显著的修改。

这个问题将会在未来Scrapy发布版本中得到解决。我们打算转化到一个新的进程模型, 并在可回收的子进程池中运行spider。

下载和处理文件和图像

Scrapy提供可重复使用的项目管道,用于下载附加到特定项目的文件(例如,当您刮擦产品并且还想在本地下载其图像时)。这些管道共享一些功能和结构(我们将它们称为介质管道),但通常您可以使用“文件管道”或“图像管道”。

两个管道都实现这些功能:

  • 避免重新下载最近下载的媒体
  • 指定存储介质的位置(文件系统目录,Amazon S3存储桶,Google云存储存储桶)

图像管道有几个额外的功能来处理图像:

  • 将所有下载的图像转换为通用格式(JPG)和模式(RGB)
  • 生成缩略图
  • 检查图像宽度/高度以确保它们符合最小限制

管道还保留当前正在计划下载的那些媒体URL的内部队列,并将包含相同媒体到达的那些响应连接到该队列。这避免了多个项目共享多次下载相同的媒体。

使用文件管道

典型的工作流程FilesPipeline如下所示:

  1. 在一个爬虫里,您抓取一个项目并将所需的URL放入一个 file_urls字段中。
  2. 该项目从爬虫内返回并转到物品管道。
  3. 当该项目进入FilesPipeline,该file_urls字段中的URL 将使用标准的Scrapy调度器和下载器(这意味着调度器和下载器中间件可以重复使用)计划下载,但具有更高的优先级,会在其他页面被抓取前处理。该项目在该特定管道阶段保持“锁定(locker)”状态,直到文件完成下载(或由于某种原因未完成下载)。
  4. 下载文件时,另一个字段(files)将被更新到结构中。该字段将包含一个含有下载文件信息的字典列表,例如下载的路径,原始抓取的URL(从file_urls字段获取)和文件校验码。该files字段列表中的文件将保留原始file_urls字段的相同顺序。如果某个文件下载失败,则会记录下错误信息并且该文件不会出现在该files字段中。

使用图像管道

使用ImagesPipeline很像使用FilesPipeline,除了使用的默认字段名称不同:您的image_urls用于项目的图像URL,它将填充images字段以获取有关下载图像的信息。

对图像文件使用ImagesPipeline的优点是,您可以配置一些额外的功能,例如生成缩略图和根据图像大小过滤图像。

Images Pipeline使用Pillow将图像缩略图和规格化为JPEG / RGB格式,因此您需要安装此库才能使用它。 在大多数情况下,Python成像库(PIL)也应该可以工作,但是在某些设置中会导致麻烦,所以我们建议使用Pillow而不是PIL。

启用媒体管道

要启用媒体管道,您必须先将其添加到您的项目 ITEM_PIPELINES设置中。

对于图像管道,请使用:

1
ITEM_PIPELINES = {'scrapy.pipelines.images.ImagesPipeline': 1}

对于文件管道,请使用:

1
ITEM_PIPELINES = {'scrapy.pipelines.files.FilesPipeline': 1}

注意

您也可以同时使用文件和图像管道。

然后,将目标存储设置配置为用于存储下载图像的有效值。否则,管道将保持禁用状态,即使您将其包含在ITEM_PIPELINES设置中。

对于文件管道,请设置以下FILES_STORE设置:

1
FILES_STORE = '/path/to/valid/dir'

对于图像管线,设置IMAGES_STORE设置:

1
IMAGES_STORE = '/path/to/valid/dir'

支持的存储

文件系统目前是唯一官方支持的存储,但也支持在Amazon S3Google云存储中存储文件。

文件系统存储

这些文件使用它们的URL 的SHA1散列来存储文件名。

例如,以下图片网址:

1
http://www.example.com/image.jpg

谁的SHA1hash是:

1
3afec3b4765f8f0a07b78f98c07b83f013567a0a

将被下载并存储在以下文件中:

1
<IMAGES_STORE>/full/3afec3b4765f8f0a07b78f98c07b83f013567a0a.jpg

哪里:

  • <IMAGES_STORE>IMAGES_STORE图像管道设置中定义的目录。
  • full是将完整图像与缩略图分开的子目录(如果使用的话)。有关更多信息,请参阅图像的缩略图生成

Amazon S3存储

FILES_STOREIMAGES_STORE可以代表Amazon S3存储桶。Scrapy会自动将文件上传到存储桶。

例如,这是一个有效的IMAGES_STORE值:

1
IMAGES_STORE = 's3://bucket/images'

您可以修改用于存储文件的访问控制列表(ACL)策略,这是由FILES_STORE_S3_ACLIMAGES_STORE_S3_ACL设置定义的。默认情况下,ACL被设置为 private。要使文件公开可用,请使用以下public-read 策略:

1
IMAGES_STORE_S3_ACL = 'public-read'

有关更多信息,请参阅Amazon S3开发人员指南中的预装ACL

Google云端存储

FILES_STOREIMAGES_STORE可以代表Google云存储存储分区。Scrapy会自动将文件上传到存储桶。(需要谷歌云存储

例如,这些是有效的IMAGES_STOREGCS_PROJECT_ID设置:

1
2
IMAGES_STORE = 'gs://bucket/images/'
GCS_PROJECT_ID = 'project_id'

有关身份验证的信息,请参阅此文档

为了首先使用媒体管道,启用它

然后,如果spider用URLs键(file_urls或者 image_urls分别为文件或图像管线)返回字典,管道将把结果放在相应的键(filesimages)下。

如果您更喜欢使用Item,那么使用必要的字段定义一个自定义项目,例如图像管道的示例:

1
2
3
4
5
6
7
import scrapy

class MyItem(scrapy.Item):

# ... other item fields ...
image_urls = scrapy.Field()
images = scrapy.Field()

如果要为URL键或结果键使用另一个字段名称,也可以覆盖它。

对于文件管道,设置FILES_URLS_FIELD和/或 FILES_RESULT_FIELD设置:

1
2
FILES_URLS_FIELD = 'field_name_for_your_files_urls'
FILES_RESULT_FIELD = 'field_name_for_your_processed_files'

对于图像管线,设置IMAGES_URLS_FIELD和/或 IMAGES_RESULT_FIELD设置:

1
2
IMAGES_URLS_FIELD = 'field_name_for_your_images_urls'
IMAGES_RESULT_FIELD = 'field_name_for_your_processed_images'

如果您需要更复杂的内容并希望覆盖自定义管道行为,请参阅扩展介质管道

如果您有多个图像管道从ImagePipeline继承,并且您希望在不同管道中具有不同的设置,则可以设置以管道类的大写名称开头的设置键。例如,如果您的管道被称为MyPipeline,并且您想定制IMAGES_URLS_FIELD,则可以定义设置MYPIPELINE_IMAGES_URLS_FIELD并使用您的自定义设置。

附加功能

文件到期

图像管道避免了下载最近下载的文件。要调整此保留延迟,请使用FILES_EXPIRES设置(或者 IMAGES_EXPIRES在图像管道的情况下),该设置指定延迟天数:

1
2
3
4
5
# 120 days of delay for files expiration
FILES_EXPIRES = 120

# 30 days of delay for images expiration
IMAGES_EXPIRES = 30

这两个设置的默认值是90天。

如果你有管道的子类FilesPipeline,你想有不同的设置,你可以设置以大写的类名开头的设置键。例如,给定管道类名为MyPipeline,您可以设置设置键:

MYPIPELINE_FILES_EXPIRES = 180

并且管道类MyPipeline将到期时间设置为180。

为图像生成缩略图

图像管道可以自动创建下载图像的缩略图。

为了使用此功能,您必须设置IMAGES_THUMBS一个字典,其中的键是缩略图名称,值是它们的尺寸。

例如:

1
2
3
4
IMAGES_THUMBS = {
'small': (50, 50),
'big': (270, 270),
}

当您使用此功能时,图像管道将使用以下格式创建每个指定尺寸的缩略图:

1
<IMAGES_STORE>/thumbs/<size_name>/<image_id>.jpg

哪里:

使用smallbig缩略图名称存储的图像文件示例:

1
2
3
<IMAGES_STORE>/full/63bbfea82b8880ed33cdb762aa11fab722a90a24.jpg
<IMAGES_STORE>/thumbs/small/63bbfea82b8880ed33cdb762aa11fab722a90a24.jpg
<IMAGES_STORE>/thumbs/big/63bbfea82b8880ed33cdb762aa11fab722a90a24.jpg

第一个是从网站下载的完整图像。

过滤掉小图片

使用图像管线时,可以通过指定IMAGES_MIN_HEIGHTIMAGES_MIN_WIDTH设置中允许的最小尺寸来放置太小的图像。

例如:

1
2
IMAGES_MIN_HEIGHT = 110 
IMAGES_MIN_WIDTH = 110

注意

大小限制完全不影响缩略图生成。

可以只设置一个尺寸约束或者两者兼有。设置它们时,只会保存同时满足最小尺寸的图像。对于上述示例,大小(105 x 105)或(105 x 200)或(200 x 105)的图像将全部被删除,因为至少一个维度比约束更短。

默认情况下,没有大小限制,因此所有图像都被处理。

允许重定向

默认情况下,媒体管道忽略重定向,即对媒体文件URL请求的HTTP重定向意味着媒体下载被认为失败。

要处理媒体重定向,请将此设置设置为True

1
MEDIA_ALLOW_REDIRECTS = True

扩展媒体管道

在这里看到你可以在自定义文件管道中覆盖的方法:

classscrapy.pipelines.files.`FilesPipeline`

  • get_media_requests(item, info)

    如工作流程所示,管道将获取要从项目下载的图像的URL。为了做到这一点,您可以覆盖该 get_media_requests()方法并为每个文件URL返回一个请求:

    1
    2
    3
    def get_media_requests(self, item, info):
    for file_url in item['file_urls']:
    yield scrapy.Request(file_url)

这些请求将由管道处理,并且当它们完成下载时,结果将会作为2-element元祖的列表发送到item_completed()。每个元组将包含以下内容:(success, file_info_or_error)

  • success是一个布尔值,如果图像下载成功则返回True 由于某种原因失败了返回False
  • file_info_or_error是一个包含以下键(如果成功返回Ture )的字典,或者如果出现了问题,则是 Twisted Failure

接收到的元组列表item_completed()保证保持从该get_media_requests()方法返回的请求的相同顺序 。

这是一个典型的results参数值:

1
2
3
4
5
6
7
8
9
10
[(True,
{'checksum': '2b00042f7481c7b056c4b410d28f33cf',
'path': 'full/7d97e98f8af710c7e7fe703abc8f639e0ee507c4.jpg',
'url': 'http://www.example.com/images/product1.jpg'}),
(True,
{'checksum': 'b9628c4ab9b595f72f280b90c4fd093d',
'path': 'full/1ca5879492b8fd606df1964ea3c1e2f4520f076f.jpg',
'url': 'http://www.example.com/images/product2.jpg'}),
(False,
Failure(...))]

默认情况下,该get_media_requests()方法返回None,这意味着没有文件要下载的项目。

item_completed(results, items, info)

FilesPipeline.item_completed()当单个项目的所有文件请求都已完成(完成下载或由于某种原因失败)时调用此方法。

item_completed()方法必须返回将发送到后续项目管道阶段的输出,因此您必须返回(或丢弃)该项目,就像在任何管道中一样。

以下是item_completed()我们将下载的文件路径(传递到结果中)存储在file_paths 项目字段中的方法示例,如果项目不包含任何文件,我们将删除该项目:

1
2
3
4
5
6
7
8
from scrapy.exceptions import DropItem

def item_completed(self, results, item, info):
image_paths = [x['path'] for ok, x in results if ok]
if not image_paths:
raise DropItem("Item contains no images")
item['image_paths'] = image_paths
return item

默认情况下,该item_completed()方法返回该项目。

在这里看到您可以在自定义图像管道中覆盖的方法:

classscrapy.contrib.pipeline.images.ImagesPipeline

​ 这ImagesPipelineFilesPipeline对字段名称进行自定义并为图像添加自定义行为的扩展。

get_media_requestsiteminfo

​ 以与方法相同的方式工作FilesPipeline.get_media_requests(),但对图像网址使用不同的字段名称。

​ 必须为每个图片网址返回一个请求。

item_completed(results, item, info)

ImagesPipeline.item_completed()当单个项目的所有图像请求都已完成(完成下载或由于某种原因失败)时调用此方法。

​ 以与方法相同的方式工作FilesPipeline.item_completed(),但使用不同的字段名称来存储图像下载结果。

​ 默认情况下,该item_completed()方法返回该项目。

自定义图像管道示例

下面是图像管道的完整示例,其示例方法如上所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import scrapy
from scrapy.pipelines.images import ImagesPipeline
from scrapy.exceptions import DropItem

class MyImagesPipeline(ImagesPipeline):

def get_media_requests(self, item, info):
for image_url in item['image_urls']:
yield scrapy.Request(image_url)

def item_completed(self, results, item, info):
image_paths = [x['path'] for ok, x in results if ok]
if not image_paths:
raise DropItem("Item contains no images")
item['image_paths'] = image_paths
return item

部署Spider

本节介绍您为部署Scrapy蜘蛛而定期运行它们的不同选项。在本地机器上运行Scrapy蜘蛛程序对于(早期)开发阶段来说非常方便,但是当您需要执行长时间运行的蜘蛛或移动蜘蛛来持续运行时,并不是那么重要。这是部署Scrapy蜘蛛解决方案的地方。

部署Scrapy蜘蛛的普遍选择是:

部署到Scrapyd服务器

Scrapyd是运行Scrapy蜘蛛的开源应用程序。它提供了一个HTTP API的服务器,能够运行和监控Scrapy蜘蛛。

要将Spider部署到Scrapyd,您可以使用由scrapyd-client包提供的scrapyd-deploy工具。请参阅scrapyd-deploy文档以获取更多信息。

Scrapyd由一些Scrapy开发人员维护。

部署到Scrapy Cloud

Scrapy CloudScrapy背后的Scrapinghub托管的基于云的服务。

Scrapy Cloud不需要安装和监控服务器,并提供了一个很好的用户界面来管理蜘蛛并查看抓取的项目,日志和统计信息。

要将Scider部署到Scrapy Cloud,您可以使用shub命令行工具。请参阅Scrapy Cloud文档以获取更多信息。

Scrapy Cloud与Scrapyd兼容,并且可以根据需要在它们之间切换 - 从scrapy.cfg文件中读取配置scrapyd-deploy

自动限速(AutoThrottle)扩展

该扩展能根据Scrapy服务器及您爬取的网站的负载自动限制爬取速度。

设计目标

  1. 更友好的对待网站,而不使用默认的下载延迟0。
  2. 自动调整scrapy来优化下载速度,使得用户不用调节下载延迟及并发请求数来找到优化的值。 用户只需指定允许的最大并发请求数,剩下的都交给扩展来完成。

怎么运行的

AutoThrottle扩展可动态调整下载延迟,以使蜘蛛向AUTOTHROTTLE_TARGET_CONCURRENCY每个远程网站平均发送 并发请求。

它使用下载延迟来计算延迟。其主要思想是:如果一台服务器需要latency秒钟响应,客户端应该发送一个请求的每个latency/N秒,具有N并行处理的请求。

而不是调整延迟,可以设置一个小的固定下载延迟并对并发使用CONCURRENT_REQUESTS_PER_DOMAINCONCURRENT_REQUESTS_PER_IP选项施加硬性限制 。它会提供类似的效果,但有一些重要的区别:

  • 由于下载延迟很小,偶尔会出现一些请求;
  • 通常非200(错误)响应可以比常规响应更快地返回,因此,在服务器开始返回错误时,如果下载延迟较小,并发限制爬网程序将更快地向服务器发送请求。但这与爬虫应该做的事情相反 - 如果发生错误,减慢速度更有意义:这些错误可能是由高请求率造成的。

AutoThrottle没有这些问题。

节流算法

AutoThrottle算法根据以下规则调整下载延迟:

  1. 蜘蛛总是以下载延迟开始 AUTOTHROTTLE_START_DELAY;
  2. 当接收到响应时,目标下载延迟被计算为 其中响应的等待时间,并且是。latency / N`latencyN[AUTOTHROTTLE_TARGET_CONCURRENCY`](https://doc.scrapy.org/en/latest/topics/autothrottle.html#std:setting-AUTOTHROTTLE_TARGET_CONCURRENCY)
  3. 下次请求的下载延迟设置为上次下载延迟的平均值和目标下载延迟;
  4. 不允许200个响应的延迟减少延迟;
  5. 下载延迟不能小于DOWNLOAD_DELAY或大于AUTOTHROTTLE_MAX_DELAY

注意

AutoThrottle扩展支持标准Scrapy设置的并发性和延迟。这意味着它会尊重 CONCURRENT_REQUESTS_PER_DOMAINCONCURRENT_REQUESTS_PER_IP选择,永远不会将下载延迟设置为低于DOWNLOAD_DELAY

在Scrapy中,下载延迟时间的测量是建立TCP连接和接收HTTP头之间的时间。

请注意,这些延迟在协作式多任务环境中很难准确测量,因为Scrapy可能正忙于处理Spider回调,并且无法参加下载。但是,这些延迟仍应该对Scrapy(最终是服务器)的繁忙程度进行合理估计,并且此扩展建立在此前提之上。

设置

用于控制AutoThrottle扩展的设置是:

有关更多信息,请参阅它如何工作

AUTOTHROTTLE_ENABLED

默认: False

启用AutoThrottle扩展。

AUTOTHROTTLE_START_DELAY

默认: 5.0

最初的下载延迟(以秒为单位)。

AUTOTHROTTLE_MAX_DELAY

默认: 60.0

在高延迟情况下设置的最大下载延迟(以秒为单位)。

AUTOTHROTTLE_TARGET_CONCURRENCY

版本1.1中的新功能

默认: 1.0

Scrapy应平行发送到远程网站的平均请求数量。

默认情况下,AutoThrottle调整延迟以向每个远程网站发送单个并发请求。将此选项设置为更高的值(例如2.0)以增加吞吐量和远程服务器的负载。较低的AUTOTHROTTLE_TARGET_CONCURRENCY值(例如0.5)会使抓取工具更加保守和礼貌。

请注意,CONCURRENT_REQUESTS_PER_DOMAINCONCURRENT_REQUESTS_PER_IP在启用AutoThrottle扩展选项仍然遵守。这意味着如果 AUTOTHROTTLE_TARGET_CONCURRENCY设置的值高于 CONCURRENT_REQUESTS_PER_DOMAIN或者CONCURRENT_REQUESTS_PER_IP,搜寻器将不会达到这个并发请求数。

在每个给定的时间点,Scrapy可以发送比或多或少的并发请求AUTOTHROTTLE_TARGET_CONCURRENCY; 它是爬虫尝试接近的建议值,而不是硬限制。

AUTOTHROTTLE_DEBUG

默认: False

启用AutoThrottle调试模式,该模式将显示收到的每个响应的统计数据,以便您可以看到如何实时调整调节参数。

Benchmarking

0.17 新版功能.

Scrapy提供了一个简单的性能测试工具。其创建了一个本地HTTP服务器,并以最大可能的速度进行爬取。 该测试性能工具目的是测试Scrapy在您的硬件上的效率,来获得一个基本的底线用于对比。 其使用了一个简单的spider,仅跟进链接,不做任何处理。

运行:

1
scrapy bench

您能看到类似的输出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
2016-12-16 21:18:48 [scrapy.utils.log] INFO: Scrapy 1.2.2 started (bot: quotesbot)
2016-12-16 21:18:48 [scrapy.utils.log] INFO: Overridden settings: {'CLOSESPIDER_TIMEOUT': 10, 'ROBOTSTXT_OBEY': True, 'SPIDER_MODULES': ['quotesbot.spiders'], 'LOGSTATS_INTERVAL': 1, 'BOT_NAME': 'quotesbot', 'LOG_LEVEL': 'INFO', 'NEWSPIDER_MODULE': 'quotesbot.spiders'}
2016-12-16 21:18:49 [scrapy.middleware] INFO: Enabled extensions:
['scrapy.extensions.closespider.CloseSpider',
'scrapy.extensions.logstats.LogStats',
'scrapy.extensions.telnet.TelnetConsole',
'scrapy.extensions.corestats.CoreStats']
2016-12-16 21:18:49 [scrapy.middleware] INFO: Enabled downloader middlewares:
['scrapy.downloadermiddlewares.robotstxt.RobotsTxtMiddleware',
'scrapy.downloadermiddlewares.httpauth.HttpAuthMiddleware',
'scrapy.downloadermiddlewares.downloadtimeout.DownloadTimeoutMiddleware',
'scrapy.downloadermiddlewares.defaultheaders.DefaultHeadersMiddleware',
'scrapy.downloadermiddlewares.useragent.UserAgentMiddleware',
'scrapy.downloadermiddlewares.retry.RetryMiddleware',
'scrapy.downloadermiddlewares.redirect.MetaRefreshMiddleware',
'scrapy.downloadermiddlewares.httpcompression.HttpCompressionMiddleware',
'scrapy.downloadermiddlewares.redirect.RedirectMiddleware',
'scrapy.downloadermiddlewares.cookies.CookiesMiddleware',
'scrapy.downloadermiddlewares.stats.DownloaderStats']
2016-12-16 21:18:49 [scrapy.middleware] INFO: Enabled spider middlewares:
['scrapy.spidermiddlewares.httperror.HttpErrorMiddleware',
'scrapy.spidermiddlewares.offsite.OffsiteMiddleware',
'scrapy.spidermiddlewares.referer.RefererMiddleware',
'scrapy.spidermiddlewares.urllength.UrlLengthMiddleware',
'scrapy.spidermiddlewares.depth.DepthMiddleware']
2016-12-16 21:18:49 [scrapy.middleware] INFO: Enabled item pipelines:
[]
2016-12-16 21:18:49 [scrapy.core.engine] INFO: Spider opened
2016-12-16 21:18:49 [scrapy.extensions.logstats] INFO: Crawled 0 pages (at 0 pages/min), scraped 0 items (at 0 items/min)
2016-12-16 21:18:50 [scrapy.extensions.logstats] INFO: Crawled 70 pages (at 4200 pages/min), scraped 0 items (at 0 items/min)
2016-12-16 21:18:51 [scrapy.extensions.logstats] INFO: Crawled 134 pages (at 3840 pages/min), scraped 0 items (at 0 items/min)
2016-12-16 21:18:52 [scrapy.extensions.logstats] INFO: Crawled 198 pages (at 3840 pages/min), scraped 0 items (at 0 items/min)
2016-12-16 21:18:53 [scrapy.extensions.logstats] INFO: Crawled 254 pages (at 3360 pages/min), scraped 0 items (at 0 items/min)
2016-12-16 21:18:54 [scrapy.extensions.logstats] INFO: Crawled 302 pages (at 2880 pages/min), scraped 0 items (at 0 items/min)
2016-12-16 21:18:55 [scrapy.extensions.logstats] INFO: Crawled 358 pages (at 3360 pages/min), scraped 0 items (at 0 items/min)
2016-12-16 21:18:56 [scrapy.extensions.logstats] INFO: Crawled 406 pages (at 2880 pages/min), scraped 0 items (at 0 items/min)
2016-12-16 21:18:57 [scrapy.extensions.logstats] INFO: Crawled 438 pages (at 1920 pages/min), scraped 0 items (at 0 items/min)
2016-12-16 21:18:58 [scrapy.extensions.logstats] INFO: Crawled 470 pages (at 1920 pages/min), scraped 0 items (at 0 items/min)
2016-12-16 21:18:59 [scrapy.core.engine] INFO: Closing spider (closespider_timeout)
2016-12-16 21:18:59 [scrapy.extensions.logstats] INFO: Crawled 518 pages (at 2880 pages/min), scraped 0 items (at 0 items/min)
2016-12-16 21:19:00 [scrapy.statscollectors] INFO: Dumping Scrapy stats:
{'downloader/request_bytes': 229995,
'downloader/request_count': 534,
'downloader/request_method_count/GET': 534,
'downloader/response_bytes': 1565504,
'downloader/response_count': 534,
'downloader/response_status_count/200': 534,
'finish_reason': 'closespider_timeout',
'finish_time': datetime.datetime(2016, 12, 16, 16, 19, 0, 647725),
'log_count/INFO': 17,
'request_depth_max': 19,
'response_received_count': 534,
'scheduler/dequeued': 533,
'scheduler/dequeued/memory': 533,
'scheduler/enqueued': 10661,
'scheduler/enqueued/memory': 10661,
'start_time': datetime.datetime(2016, 12, 16, 16, 18, 49, 799869)}
2016-12-16 21:19:00 [scrapy.core.engine] INFO: Spider closed (closespider_timeout)

这说明了您的Scrapy能以3900页面/分钟的速度爬取。注意,这是一个非常简单,仅跟进链接的spider。 任何您所编写的spider会做更多处理,从而减慢爬取的速度。 减慢的程度取决于spider做的处理以及其是如何被编写的。

未来会有更多的用例会被加入到性能测试套装中,以覆盖更多常见的情景。

CATALOG
  1. 1. 通用爬虫(Broad Crawls)
    1. 1.1. 增加并发
    2. 1.2. 增加Twisted IO线程池的最大大小
    3. 1.3. 设置你自己的DNS
    4. 1.4. 降低log级别
    5. 1.5. 禁止cookies
    6. 1.6. 禁止重试
    7. 1.7. 减小下载超时
    8. 1.8. 禁止重定向
    9. 1.9. 启用 “Ajax Crawlable Pages” 爬取
  2. 2. 借助Firefox来爬取
    1. 2.1. 在浏览器中检查DOM的注意事项
    2. 2.2. 对爬取有帮助的实用Firefox插件
      1. 2.2.1. Firebug
      2. 2.2.2. XPather
      3. 2.2.3. XPath Checker
      4. 2.2.4. Tamper Data
      5. 2.2.5. Firecookie
  3. 3. 使用Firebug进行爬取
    1. 3.1. 介绍
    2. 3.2. 获取到跟进(follow)的链接
    3. 3.3. 提取数据
  4. 4. 调试内存溢出
    1. 4.1. 内存泄露的常见原因
      1. 4.1.1. 请求过多?
    2. 4.2. 使用 trackref 调试内存泄露
      1. 4.2.1. 哪些对象被追踪了?
      2. 4.2.2. 真实例子
      3. 4.2.3. 很多spider?
      4. 4.2.4. scrapy.utils.trackref模块
    3. 4.3. 使用Guppy调试内存泄露
    4. 4.4. Leaks without leaks
  5. 5. 下载和处理文件和图像
    1. 5.1. 使用文件管道
    2. 5.2. 使用图像管道
    3. 5.3. 启用媒体管道
    4. 5.4. 支持的存储
      1. 5.4.1. 文件系统存储
      2. 5.4.2. Amazon S3存储
      3. 5.4.3. Google云端存储
    5. 5.5. 附加功能
      1. 5.5.1. 文件到期
      2. 5.5.2. 为图像生成缩略图
      3. 5.5.3. 过滤掉小图片
      4. 5.5.4. 允许重定向
    6. 5.6. 扩展媒体管道
    7. 5.7. 自定义图像管道示例
  6. 6. 部署Spider
    1. 6.1. 部署到Scrapyd服务器
    2. 6.2. 部署到Scrapy Cloud
  7. 7. 自动限速(AutoThrottle)扩展
    1. 7.1. 设计目标
    2. 7.2. 怎么运行的
    3. 7.3. 节流算法
    4. 7.4. 设置
      1. 7.4.1. AUTOTHROTTLE_ENABLED
      2. 7.4.2. AUTOTHROTTLE_START_DELAY
      3. 7.4.3. AUTOTHROTTLE_MAX_DELAY
      4. 7.4.4. AUTOTHROTTLE_TARGET_CONCURRENCY
      5. 7.4.5. AUTOTHROTTLE_DEBUG
  8. 8. Benchmarking